﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Net.Mail;
using System.Net;
using System.Diagnostics;
using System.Threading;
using System.Configuration;
using System.Data.SqlClient;

namespace NewsletterMailingService
{
    //Note: actually database connection are established and closed every 1/2 seconds during publication process. Might be a good idea to let one connection
    //opened. See IncrementCurrent

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    class NewsletterService:InewsletterService
    {
        public NewsletterService()
        {
            this.connectionString = ConfigurationManager.ConnectionStrings["ServiceMailDb"].ConnectionString;
            this.Configure(
                ConfigurationManager.AppSettings["Smtp_address"],
                ConfigurationManager.AppSettings["Smtp_login"],
                ConfigurationManager.AppSettings["Smtp_password"],
                int.Parse(ConfigurationManager.AppSettings["Smtp_port"]),
                ConfigurationManager.AppSettings["Smtp_sender"]
                );
        }

        private void Configure(string smtpAdress, string login, string password, int port, string sender)
        {
            this.smtpAddress = smtpAdress;
            this.login = login;
            this.password = password;
            this.port = port;
            this.sender = sender;
        }

        private string connectionString;

        /// <summary>
        /// Returns the state of the current newsletter wich is being publicated or NULL
        /// </summary>
        /// <returns></returns>
        public Infos GetState()
        {
            Infos info = null;

            //We select a log where StartDateTime is the most current and EndDateTime is null (means publication processe is not over yet)
            string query = @"SELECT 
                          [CurrentMailsSent]
                          ,[TotalMailsToSend]
                      FROM [NewsletterService].[dbo].[Log]
                        WHERE ([EndDateTime] IS null AND [StartDateTime] =
                                  (
                                  SELECT MAX([StartDateTime])
                                  FROM [NewsletterService].[dbo].[Log]
	                                ))";

            SqlCommand command = new SqlCommand(query);

            using (SqlConnection connection = new SqlConnection(this.connectionString))
            {
                command.Connection = connection;
                connection.Open();

                using(SqlDataReader reader = command.ExecuteReader())
                {
                    if(reader.Read())
                    {
                        info = new Infos();
                        info.MailsSent = (int)reader["CurrentMailsSent"];
                        info.TotalMailsToSend = (int)reader["TotalMailsToSend"];
                    }
                }
            }

            return info;

        }

        /// <summary>
        /// Returns top 100 newsletter publication logs
        /// </summary>
        /// <returns></returns>
        public List<Infos> GetStatistics()
        {
            List<Infos> infos = new List<Infos>();

            string query = @"SELECT TOP 100 [StartDateTime]
                          ,[EndDateTime]
                          ,[CurrentMailsSent]
                          ,[TotalMailsToSend]
                      FROM [NewsletterService].[dbo].[Log]
                      ORDER BY [StartDateTime] Desc";

            SqlCommand command = new SqlCommand(query);

            using (SqlConnection connection = new SqlConnection(this.connectionString))
            {
                command.Connection = connection;
                connection.Open();

                using (SqlDataReader reader = command.ExecuteReader())
                {
                    while(reader.Read())
                    {
                        Infos info = new Infos();
                        info.StartingDateTime = (DateTime)reader["StartDateTime"];
                        info.EndingDate = reader["EndDateTime"].GetType() != typeof(System.DBNull)?
                            (DateTime)reader["EndDateTime"]:DateTime.MaxValue;
                        info.MailsSent = (int)reader["CurrentMailsSent"];
                        info.TotalMailsToSend = (int)reader["TotalMailsToSend"];

                        infos.Add(info);
                    }
                }

            }

            return infos;
        }
        /// <summary>
        /// Called when a newsletter publication process starts to update database
        /// </summary>
        /// <param name="totalMailsToSend"></param>
        private void CreateCurrentState(int totalMailsToSend)
        {
            string request = @"INSERT INTO [NewsletterService].[dbo].[Log]
                                       ([StartDateTime]
                                       ,[CurrentMailsSent]
                                       ,[TotalMailsToSend])
                                 VALUES
                                       (@StartDateTime
                                       ,0
                                       ,@TotalMailsToSend)";

            SqlCommand command = new SqlCommand(request);
            command.Parameters.AddWithValue("@StartDateTime", DateTime.Now);
            command.Parameters.AddWithValue("@TotalMailsToSend", totalMailsToSend);

            using (SqlConnection connection = new SqlConnection(this.connectionString))
            {
                command.Connection = connection;
                connection.Open();

                command.ExecuteNonQuery();
            }

        }

        /// <summary>
        /// Increments the number of mails sent during the current publication process
        /// </summary>
        private void IncrementCurrent()
        {
            string request = @"UPDATE [NewsletterService].[dbo].[Log] SET [CurrentMailsSent] = [CurrentMailsSent] + 1 
                                  WHERE ([EndDateTime] IS null AND [StartDateTime] =
                                  (
                                  SELECT MAX([StartDateTime])
                                  FROM [NewsletterService].[dbo].[Log]
	                                )
                                  )";

            SqlCommand command = new SqlCommand(request);

            using (SqlConnection connection = new SqlConnection(this.connectionString))
            {
                command.Connection = connection;
                connection.Open();

                command.ExecuteNonQuery();
            }
        }

        /// <summary>
        /// Set end date for the current newsletter publication process. 
        /// </summary>
        private void TerminateCurrent()
        {
            string request = @"UPDATE [NewsletterService].[dbo].[Log] SET [EndDateTime] = @EndDateTime
  WHERE ([EndDateTime] IS null AND [StartDateTime] =
  (
  SELECT MAX([StartDateTime])
  FROM [NewsletterService].[dbo].[Log]
	)
  )";

            SqlCommand command = new SqlCommand(request);
            command.Parameters.AddWithValue("@EndDateTime", DateTime.Now);

            using (SqlConnection connection = new SqlConnection(this.connectionString))
            {
                command.Connection = connection;
                connection.Open();

                command.ExecuteNonQuery();
            }
        }
        /// <summary>
        /// Send a newsletter with a body (message) and a title (title) to users (destinataires)
        /// </summary>
        /// <param name="destinataires">Users to send the newsletter to</param>
        /// <param name="message">Body of the newsletter</param>
        /// <param name="title">Title of the newsletter</param>
        /// <returns></returns>
        public bool SendMail(Dictionary<int, string> destinataires, string message, string title)
        {
            //On log les informations sur la newsletter 
            //We create log record for the newsletter
            this.CreateCurrentState(destinataires.Count);

            //On pose le thread pour que le javascript côté client puisse savoir qu'on a démarré la publication
            //(utile uniquement si il y-a peu de destinataire (1/2) car le js ne se met pas à jour en temp réel

            //Tricky move. We stop the current thread so the javascript client side can be aware that newsletter publication
            //has started. It's useful in case newsletter is sent to few people.
            Thread.Sleep(2000);

            //It might help you to find issues.
            TextWriterTraceListener traceListener = new TextWriterTraceListener("C:/debugNewsletter.log");

            Debug.Listeners.Add(traceListener);
            Debug.AutoFlush = true;

            try
            {
                //On envoie les mails
                foreach (KeyValuePair<int, string> dest in destinataires)
                {
                    //Send the newsletter to one user
                    this.SendMail(dest, message, title);

                    //Update database
                    this.IncrementCurrent();
                }
            }
            catch(Exception e)
            {
                Debug.WriteLine("------------------------------------------------------");
                Debug.WriteLine(e.ToString());
                Debug.WriteLine("");
                Debug.WriteLine("Variables:");
                Debug.WriteLine("SMTP adresse:" + this.smtpAddress);
                Debug.WriteLine("SMTP port:" + this.port);
                Debug.WriteLine("SMTP login:" + this.login);
                Debug.WriteLine("SMTP mot de passe:" + this.password);
                Debug.WriteLine("SMTP Sender:" + this.sender);
                Debug.WriteLine("------------------------------------------------------");
            }
            finally
            {
                //On insère la date de fin en bdd
                //update database record
                this.TerminateCurrent();
                //Close listener
                traceListener.Close();
                Debug.Listeners.Clear();
            }
            return true;
        }


        /// <summary>
        /// Envoi un mail au destinataire
        /// Send a email to an user
        /// </summary>
        /// <param name="destinataire"></param>
        /// <param name="message"></param>
        /// <param name="title"></param>
        private void SendMail(KeyValuePair<int, string> destinataire, string message, string title)
        {
            //Timer is here only to limit mails sent every seconds. You can remove it depending off you SMTP.
            Thread.Sleep(500);

            //remplacement de la variable.
            //Replace [desinscription] tag by a personnal unsubscription link
            message = message.Replace("[desinscription]", "token=" + destinataire.Key.ToString() + "&mail=" + destinataire.Value);

            //Expéditeur
            //Sender
            MailAddress addresseSender = new MailAddress(this.login, this.sender);

            //Destinataire
            MailAddress addresseDest = new MailAddress(destinataire.Value);

            //création d'un email
            //Email creation
            MailMessage email = new MailMessage(addresseSender, addresseDest);
            //email.From = this.from;
            email.Subject = title;
            email.Body = message;

            email.IsBodyHtml = true;

            SmtpClient smtp = new SmtpClient(this.smtpAddress, this.port);
            //SSL or not... Might be a good idea to include that in the config file
            //smtp.EnableSsl = true;
            smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
            smtp.Credentials = new NetworkCredential(this.login, this.password);
            smtp.EnableSsl = true;

            //Envoie du mail
            //We send the email
            smtp.Send(email);

        }

        /// <summary>
        /// Configuration du smtp
        /// </summary>
        private string smtpAddress, login, password, sender;
        private int port;

    }
}
